namespace Angelus.Oracle.Entity.Common
{
    using System;
    using System.Collections.Generic;
    using System.Data.Metadata.Edm;

    /// <summary>
    /// Description of MetadataHelpers.
    /// </summary>
    internal static class MetadataHelpers
    {
        #region Constants

        internal const string BuiltInMetadata = "BuiltInAttribute";
        private const string DefaultValueFacetName = "DefaultValue";

        /// <summary>
        /// The Edm namespace name, used for canonical functions.
        /// </summary>
        private const string EdmNamespaceName = "Edm";

        private const string FixedLengthFacetName = "FixedLength";
        private const string MaxLengthFacetName = "MaxLength";
        private const string NullableFacetName = "Nullable";
        private const string PrecisionFacetName = "Precision";
        private const string ScaleFacetName = "Scale";
        private const string UnicodeFacetName = "Unicode";

        #endregion

        #region Metadata Constants

        internal const string NiladicFunctionMetadata = "NiladicFunctionAttribute";
        internal const string SchemaMetadata = "Schema";
        internal const string StoreFunctionNameMetadata = "StoreFunctionNameAttribute";
        internal const string TableMetadata = "Table";

        #endregion

        #region Metadata Properties

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="item"></param>
        /// <param name="identity"></param>
        /// <returns></returns>
        internal static T GetMetadataProperty<T>(MetadataItem item, string identity)
        {
            MetadataProperty property;
            if (!item.MetadataProperties.TryGetValue(identity, true, out property) || !(property.Value is T))
            {
                return default(T);
            }

            return (T) property.Value;
        }

        #endregion

        /// <summary>
        /// Cast the EdmType of the given type usage to the given TEdmType.
        /// </summary>
        /// <param name="usage"></param>
        /// <returns></returns>
        internal static TEdmType GetEdmType<TEdmType>(TypeUsage usage)
            where TEdmType : EdmType
        {
            return (TEdmType) usage.EdmType;
        }

        /// <summary>
        /// Gets the TypeUsage of the element if the given type is a collection type.
        /// </summary>
        /// <param name="usage"></param>
        /// <returns></returns>
        internal static TypeUsage GetElementTypeUsage(TypeUsage usage)
        {
            if (IsCollectionType(usage))
            {
                return ((CollectionType) usage.EdmType).TypeUsage;
            }

            return null;
        }

        /// <summary>
        /// Is the given TypeUsage over a collection type
        /// </summary>
        /// <param name="usage"></param>
        /// <returns></returns>
        internal static bool IsCollectionType(TypeUsage usage)
        {
            return IsCollectionType(usage.EdmType);
        }

        /// <summary>
        /// Is the given type a collection type.
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        internal static bool IsCollectionType(EdmType type)
        {
            return BuiltInTypeKind.CollectionType == type.BuiltInTypeKind;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="usage"></param>
        /// <returns></returns>
        internal static IList<EdmProperty> GetProperties(TypeUsage usage)
        {
            return GetProperties(usage.EdmType);
        }

        internal static IList<EdmProperty> GetProperties(EdmType edmType)
        {
            switch (edmType.BuiltInTypeKind)
            {
                case BuiltInTypeKind.ComplexType:
                    return ((ComplexType) edmType).Properties;

                case BuiltInTypeKind.EntityType:
                    return ((EntityType) edmType).Properties;

                case BuiltInTypeKind.RowType:
                    return ((RowType) edmType).Properties;

                default:
                    return new List<EdmProperty>();
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="usage"></param>
        /// <returns></returns>
        internal static bool IsRowType(TypeUsage usage)
        {
            return IsRowType(usage.EdmType);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="edmType"></param>
        /// <returns></returns>
        internal static bool IsRowType(EdmType edmType)
        {
            return BuiltInTypeKind.RowType == edmType.BuiltInTypeKind;
        }

        /// <summary>
        /// Is the given type usage over a primitive type
        /// </summary>
        /// <param name="usage"></param>
        /// <returns></returns>
        internal static bool IsPrimitiveType(TypeUsage usage)
        {
            return IsPrimitiveType(usage.EdmType);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="usage"></param>
        /// <param name="primiviteTypeKind"></param>
        /// <returns></returns>
        internal static bool IsPrimitiveType(TypeUsage usage, PrimitiveTypeKind primiviteTypeKind)
        {
            PrimitiveTypeKind typeKind;
            if (TryGetPrimitiveTypeKind(usage, out typeKind))
            {
                return typeKind == primiviteTypeKind;
            }
            return false;
        }

        /// <summary>
        /// Is the given type a primitive type
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        internal static bool IsPrimitiveType(EdmType type)
        {
            return BuiltInTypeKind.PrimitiveType == type.BuiltInTypeKind;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="usage"></param>
        /// <param name="typeKind"></param>
        /// <returns></returns>
        internal static bool TryGetPrimitiveTypeKind(TypeUsage usage, out PrimitiveTypeKind typeKind)
        {
            if ((null != usage) && (null != usage.EdmType) &&
                (BuiltInTypeKind.PrimitiveType == usage.EdmType.BuiltInTypeKind))
            {
                typeKind = ((PrimitiveType) usage.EdmType).PrimitiveTypeKind;
                return true;
            }

            typeKind = default(PrimitiveTypeKind);
            return false;
        }

        /// <summary>
        /// Gets the value for the metadata property with the given name.
        /// </summary>
        /// <param name="item"></param>
        /// <param name="propertyName"></param>
        /// <returns></returns>
        internal static T TryGetValueForMetadataProperty<T>(MetadataItem item, string propertyName)
        {
            MetadataProperty property;
            if (!item.MetadataProperties.TryGetValue(propertyName, true, out property))
            {
                return default(T);
            }

            return (T) property.Value;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="usage"></param>
        /// <param name="maxLength"></param>
        /// <returns></returns>
        internal static bool TryGetMaxLength(TypeUsage usage, out int maxLength)
        {
            if (!IsPrimitiveType(usage, PrimitiveTypeKind.String) && !IsPrimitiveType(usage, PrimitiveTypeKind.Binary))
            {
                maxLength = 0;
                return false;
            }

            // Binary and String FixedLength share the same name
            return TryGetIntFacetValue(usage, MaxLengthFacetName, out maxLength);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="usage"></param>
        /// <param name="name"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        internal static bool TryGetBooleanFacetValue(TypeUsage usage, string name, out bool value)
        {
            value = false;
            Facet facet;
            if (usage.Facets.TryGetValue(name, false, out facet) && (null != facet.Value))
            {
                value = (bool) facet.Value;
                return true;
            }

            return false;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="usage"></param>
        /// <param name="name"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        internal static bool TryGetByteFacetValue(TypeUsage usage, string name, out byte value)
        {
            value = 0;
            Facet facet;
            if (usage.Facets.TryGetValue(name, false, out facet) && null != facet.Value && !facet.IsUnbounded)
            {
                value = (byte) facet.Value;
                return true;
            }

            return false;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="usage"></param>
        /// <param name="name"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        internal static bool TryGetIntFacetValue(TypeUsage usage, string name, out int value)
        {
            value = 0;
            Facet facet;
            if (usage.Facets.TryGetValue(name, false, out facet) && null != facet.Value && !facet.IsUnbounded)
            {
                value = (int) facet.Value;
                return true;
            }

            return false;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="usage"></param>
        /// <param name="isFixedLength"></param>
        /// <returns></returns>
        internal static bool TryGetIsFixedLength(TypeUsage usage, out bool isFixedLength)
        {
            if (!IsPrimitiveType(usage, PrimitiveTypeKind.String) && !IsPrimitiveType(usage, PrimitiveTypeKind.Binary))
            {
                isFixedLength = false;
                return false;
            }

            // Binary and String MaxLength facets sthre the same name.
            return TryGetBooleanFacetValue(usage, FixedLengthFacetName, out isFixedLength);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="usage"></param>
        /// <param name="isUnicode"></param>
        /// <returns></returns>
        internal static bool TryGetIsUnicode(TypeUsage usage, out bool isUnicode)
        {
            if (!IsPrimitiveType(usage, PrimitiveTypeKind.String))
            {
                isUnicode = false;
                return false;
            }

            return TryGetBooleanFacetValue(usage, UnicodeFacetName, out isUnicode);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="function"></param>
        /// <returns></returns>
        internal static bool IsCanonicalFunction(EdmFunction function)
        {
            return function.NamespaceName.Equals(EdmNamespaceName, StringComparison.OrdinalIgnoreCase);
        }
    }
}